import React, { Component } from 'react';
import cx from 'classnames';
import PropTypes from 'prop-types';

import createScroll from './createScroll';
import { Loading, NoData } from './Wall';

import S from './skeleton.m.less';

import showHide from '@wpt/showHide';

export const Skeleton = {
    SaleItem: (props) => (
        <div className={cx(S.Skeleton, S.Sale, props.className)}>
            <div className={S.SaleInfo}>
                <div className={S.SaleTitle} />
                <div className={S.SaleTags} />
                <div className={S.SalePrice} />
            </div>
        </div>
    ),
    LiveItem: (props) => (
        <div className={cx(S.Skeleton, props.columns === 2 ? S.LiveSmall : S.Live, props.className)}>
            <div className={S.LiveInfo} />
        </div>
    ),
};

@showHide
export default class GoodsWall extends Component {
    static defaultProps = {
        className: '',
        endTips: false, // 到底提示
        dataTimeout: 800, // fetchKey改变后，数据接口请求超时时间后后清空当前数据
        firstScreenData: null, // 首屏数据
        fetchKey: null, // 改变该值之后，会重新请求接口刷新数据，组件更新
        preHandleData: (any) => any, // 数据前置处理函数
        scroller: null, // 滚动事件
        ajaxUrl: '', // 数据请求接口
        ajaxType: 'get', // 接口请求类型
        ajaxParam: {}, // 接口请求参数
        loadingClass: '', // 加载中样式
        loadingText: undefined, // 加载字段
        initLoading: undefined, //
        noData: undefined, //
        parent: undefined, //
        bottomHeight: 2000,
        isHandleError: false,
        skeleton: false,
        skeletonClass: undefined,
        keepScroll: false, // 更新fetchKey时保持滚动位置
    };

    static propTypes = {
        className: PropTypes.any,
        endTips: PropTypes.any,
        dataTimeout: PropTypes.number,
        firstScreenData: PropTypes.object,
        fetchKey: PropTypes.string,
        preHandleData: PropTypes.func,
        scroller: PropTypes.any,
        ajaxUrl: PropTypes.string,
        ajaxType: PropTypes.string,
        ajaxParam: PropTypes.object,
        loadingClass: PropTypes.any,
        loadingText: PropTypes.string,
        templates: PropTypes.func.isRequired,
        initLoading: PropTypes.func,
        noData: PropTypes.object,
        parent: PropTypes.object,
        bottomHeight: PropTypes.number,
        isHandleError: PropTypes.bool,
        skeleton: PropTypes.oneOfType([PropTypes.node, PropTypes.func, PropTypes.bool]),
        skeletonClass: PropTypes.string,
        keepScroll: PropTypes.bool,
    };

    scrollKey = WPT.Util.uuid();

    constructor(props) {
        super(props);
        this.state = {
            isEnd: false, // 结束加载
            items: [],
            page: '',
            start: '',
            nowTime: WPT.nowTime,
        };
        this._fetchKey = props.fetchKey;
    }

    componentDidMount() {
        this.scroll = this.getScroll();
        // 页面渲染后
        const { firstScreenData } = this.props;
        if ($.isEmptyObject(firstScreenData)) {
            this.fetch();
        } else {
            this.ajaxSuccBack(firstScreenData, this._fetchKey);
        }

        this.onPageShow();
    }

    UNSAFE_componentWillReceiveProps(props) {
        if (this._fetchKey !== props.fetchKey) {
            // 修改 _fetchKey
            this._fetchKey = props.fetchKey;
            // 取消前一次的 setTimeout
            clearTimeout(this._clearDataId);
            // 设置需要重置列表
            this._pending = true;
            // 需要触发scroll
            this._needScroll = true;
            // 初始化请求参数后重新请求
            this.setState(
                {
                    isEnd: false,
                    start: '',
                    page: '',
                    items: [],
                },
                () => {
                    this.fetch(props.keepScroll);
                }
            );

            // 设置请求超时处理
            this._clearDataId = setTimeout(() => !this._unmount && this.forceUpdate(), this.props.dataTimeout);
        }
    }

    shouldComponentUpdate() {
        return !this._pending;
    }

    componentWillUnmount() {
        this._unmount = true;
        this.onPageHide();
    }

    onPageShow() {
        this.scroll.appendScroll(this.scrollKey, this.handleScroll);
    }

    onPageHide() {
        this.scroll.clear(this.scrollKey);
    }

    setMinHeight = (keepScroll) => {
        if (!this.$container || !this.$container.style) return;
        if (keepScroll) {
            this.$container.style.cssText = `min-height: ${this.$container?.getBoundingClientRect().height || 0}px;`;
        } else {
            this.$container.style.cssText = '';
        }
    };

    getScroll = () => {
        const { scroller } = this.props;

        if (!scroller) {
            return WPT.scroll;
        }
        return createScroll($(this.$wall).parents(scroller));
    };

    ajaxSuccBack = (res, key) => {
        // 判断请求后时候重修修改了 _fetchKey，不相等则忽略该次数据
        if (key !== this._fetchKey) {
            return;
        }
        // 清除timeout
        clearTimeout(this._clearDataId);
        this._pending = false;

        const hasError = res.code && Number(res.code) !== 0;

        // 数据渲染前置处理
        if ((typeof this.props.preHandleData === 'function' && Number(res.code) === 0) || this.props.isHandleError) {
            res = this.props.preHandleData(res) || res;
        }

        // 有错误不处理后续setData
        if (hasError) {
            this.setMinHeight();
            return;
        }

        const [start, page, isEnd, serverData, items] = ['start', 'page', 'isEnd', 'serverData', 'items'].map((_key) =>
            res.items ? res[_key] : res?.data && res?.data[_key]
        );

        this.serverData = serverData;

        // start page 会在下次请求的的时候传递给后端，serverData对象会在展开后传递给后端
        this.setState(
            {
                items: this.state.items.concat(items || []),
                start,
                page,
                isEnd,
                nowTime: res.nowTime,
            },
            () => {
                this.setMinHeight();
                // 触发滚动事件
                if (this._needScroll) {
                    $(document).trigger('scroll');
                    this._needScroll = false;
                }
            }
        );
    };

    handleScroll = (scrollTop, scrollHeight, containerHeight) => {
        // scrollTop 滚动高度  scrollHeight 整个滚动条高度
        if (this.state.isEnd || this._isLoading) return;
        scrollTop = scrollTop || document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;
        // 判断滚动条高度，确定是否要加载下一屏
        const wHeight = containerHeight || document.documentElement.clientHeight;
        const dHeight = scrollHeight || document.body.scrollHeight; // document.documentElement.offsetHeight 不要了
        if (dHeight - (scrollTop + wHeight) < this.props.bottomHeight) {
            this.fetch();
        }
    };

    // 发异步请求获取数据
    fetch = (keepScroll) => {
        this._isLoading = true;

        const _fetchKey = this._fetchKey;
        const { ajaxUrl, ajaxType, ajaxParam } = this.props;
        const { start, page } = this.state;

        // 设置当前内容的最小高度
        this.setMinHeight(keepScroll);

        // 请求url
        const data = {
            start,
            page,
            ...this.serverData,
            ...ajaxParam,
        };

        let ajax = WPT.getData;
        if (ajaxType.toLowerCase() == 'post') {
            ajax = WPT.postData;
        }

        ajax(ajaxUrl, data, (res) => {
            this._isLoading = false;
            this.ajaxSuccBack(res, _fetchKey);
        });
    };

    ref = (ref) => {
        this.$wall = ref;
    };

    containerRef = (n) => {
        this.$container = n;
    };

    renderEndTips() {
        const { endTips } = this.props;
        const { state } = this;
        if (state.isEnd && state.items.length > 0) {
            if (typeof endTips === 'boolean') {
                return endTips && <div className="goodsWallEndTips" />;
            }
            return endTips;
        }
        return null;
    }

    modifyItems = (fn) => {
        const items = fn(this.state.items);
        this.setState({ items });
    };

    render() {
        const {
            loadingText,
            className,
            templates: Templates,
            initLoading,
            noData,
            parent,
            skeleton,
            skeletonClass,
            fetchKey,
        } = this.props;

        const { isEnd, items = [], page, nowTime } = this.state;

        const InitLoading = typeof initLoading === 'function' ? initLoading : Loading;
        const isNoData = isEnd && items.length === 0;
        const CusSkeleton = typeof skeleton === 'boolean' ? skeleton && Skeleton.SaleItem : skeleton;

        const style =
            isNoData && noData
                ? {
                      position: 'relative',
                      minHeight: '8rem',
                  }
                : null;

        return (
            <div className={cx('wall-container clearfix', className)} style={style} ref={this.ref}>
                <div className="wall-col clearfix" ref={this.containerRef}>
                    {CusSkeleton && !isNoData && !items?.length && (
                        <div className={cx(S.SkeletonWrap, skeletonClass)}>
                            {Array.from({ length: 6 }).map((item, i) => (
                                <CusSkeleton key={i} />
                            ))}
                        </div>
                    )}
                    <Templates
                        items={items}
                        parent={parent}
                        page={page}
                        nowTime={nowTime}
                        isEnd={isEnd}
                        isNoData={isNoData}
                        isFirstLoading={!isEnd && !page}
                        modifyItems={this.modifyItems}
                        fetchKey={fetchKey}
                    />
                </div>
                {initLoading && !isEnd && !page && <InitLoading />}
                {/* eslint-disable-next-line react/prop-types */}
                {!isEnd && page && (
                    <Loading
                        inverse={this.props.loadingInverse}
                        loadingClass={this.props.loadingClass}
                        loadingText={loadingText}
                    />
                )}
                {!!isNoData && noData && <NoData noData={noData} />}
                {this.renderEndTips()}
            </div>
        );
    }
}
